]> dgit.raspbian.org Git - gnome-shell.git/commitdiff
tray: Add NaXembed object
authorCarlos Garnacho <carlosg@gnome.org>
Wed, 7 Dec 2022 19:50:53 +0000 (20:50 +0100)
committerMarge Bot <marge-bot@gnome.org>
Fri, 3 Mar 2023 13:34:44 +0000 (13:34 +0000)
This GObject subclass is a minimal wrapper similar to GtkSocket,
meant to replace the GTK widgetry involved in the tray manager.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2590>

src/tray/meson.build
src/tray/na-xembed.c [new file with mode: 0644]
src/tray/na-xembed.h [new file with mode: 0644]

index 139e3b2972d48e96de50deeb3e94f9fa9a976dbd..092e7f689bb6e4e5897f998d5085bf6c8e6a68a0 100644 (file)
@@ -2,11 +2,13 @@ tray_sources = [
   'na-tray-child.c',
   'na-tray-child.h',
   'na-tray-manager.c',
-  'na-tray-manager.h'
+  'na-tray-manager.h',
+  'na-xembed.c',
+  'na-xembed.h'
 ]
 
 libtray = static_library('tray', tray_sources,
   c_args: ['-DG_LOG_DOMAIN="notification_area"'],
-  dependencies: [clutter_dep, gtk_dep],
+  dependencies: [mutter_dep, gtk_dep],
   include_directories: conf_inc
 )
diff --git a/src/tray/na-xembed.c b/src/tray/na-xembed.c
new file mode 100644 (file)
index 0000000..5dbb372
--- /dev/null
@@ -0,0 +1,882 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* Copyright (C) 2022 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Carlos Garnacho <carlosg@gnome.org>
+ *
+ * Based on GTK+ code by Owen Taylor <otaylor@gtk.org>
+ */
+
+#include "config.h"
+
+#include "na-xembed.h"
+
+#include <meta/meta-x11-errors.h>
+#include <X11/extensions/Xfixes.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+typedef struct _NaXembedPrivate NaXembedPrivate;
+
+struct _NaXembedPrivate
+{
+  MetaX11Display *x11_display;
+  Window socket_window;
+  Window plug_window;
+
+  int root_x;
+  int root_y;
+  int request_width;
+  int request_height;
+  int current_width;
+  int current_height;
+  int resize_count;
+  int xembed_version;
+
+  unsigned int event_func_id;
+  guint resize_id;
+
+  XVisualInfo *xvisual_info;
+
+  Atom atom__XEMBED;
+  Atom atom__XEMBED_INFO;
+  Atom atom_WM_NORMAL_HINTS;
+
+  gboolean have_size;
+  gboolean need_map;
+  gboolean is_mapped;
+  gboolean has_alpha;
+};
+
+/* XEMBED messages */
+typedef enum _XembedMessageType XembedMessageType;
+
+enum _XembedMessageType {
+  XEMBED_EMBEDDED_NOTIFY,
+  XEMBED_WINDOW_ACTIVATE,
+  XEMBED_WINDOW_DEACTIVATE,
+  XEMBED_REQUEST_FOCUS,
+  XEMBED_FOCUS_IN,
+  XEMBED_FOCUS_OUT,
+  XEMBED_FOCUS_NEXT,
+  XEMBED_FOCUS_PREV,
+  XEMBED_GRAB_KEY,
+  XEMBED_UNGRAB_KEY,
+  XEMBED_MODALITY_ON,
+  XEMBED_MODALITY_OFF,
+};
+
+/* Flags for _XEMBED_INFO */
+#define XEMBED_MAPPED (1 << 0)
+
+#define XEMBED_PROTOCOL_VERSION 1
+
+G_DEFINE_TYPE_WITH_PRIVATE (NaXembed, na_xembed, G_TYPE_OBJECT)
+
+enum {
+  PLUG_ADDED,
+  PLUG_REMOVED,
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+enum {
+  PROP_0,
+  PROP_X11_DISPLAY,
+  N_PROPS
+};
+
+static GParamSpec *props[N_PROPS] = { 0, };
+
+static void
+xembed_send_message (NaXembed          *xembed,
+                     Window             recipient,
+                     XembedMessageType  message,
+                     glong              detail,
+                     glong              data1,
+                     glong              data2)
+{
+  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);
+  XClientMessageEvent xclient;
+
+  memset (&xclient, 0, sizeof (xclient));
+  xclient.window = recipient;
+  xclient.type = ClientMessage;
+  xclient.message_type = priv->atom__XEMBED;
+  xclient.format = 32;
+  xclient.data.l[0] = 0; /* Time */
+  xclient.data.l[1] = message;
+  xclient.data.l[2] = detail;
+  xclient.data.l[3] = data1;
+  xclient.data.l[4] = data2;
+
+  meta_x11_error_trap_push (priv->x11_display);
+  XSendEvent (meta_x11_display_get_xdisplay (priv->x11_display),
+              recipient,
+              False, NoEventMask, (XEvent*) &xclient);
+  meta_x11_error_trap_pop (priv->x11_display);
+}
+
+static void
+na_xembed_end_embedding (NaXembed *xembed)
+{
+  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);
+
+  priv->plug_window = None;
+  priv->current_width = 0;
+  priv->current_height = 0;
+  priv->resize_count = 0;
+  g_clear_handle_id (&priv->resize_id, g_source_remove);
+}
+
+static void
+na_xembed_send_configure_event (NaXembed *xembed)
+{
+  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);
+  XConfigureEvent xconfigure;
+
+  memset (&xconfigure, 0, sizeof (xconfigure));
+  xconfigure.type = ConfigureNotify;
+
+  xconfigure.event = priv->plug_window;
+  xconfigure.window = priv->plug_window;
+
+  xconfigure.x = priv->root_x;
+  xconfigure.y = priv->root_y;
+  xconfigure.width = priv->current_width;
+  xconfigure.height = priv->current_height;
+
+  xconfigure.border_width = 0;
+  xconfigure.above = None;
+  xconfigure.override_redirect = False;
+
+  meta_x11_error_trap_push (priv->x11_display);
+  XSendEvent (meta_x11_display_get_xdisplay (priv->x11_display),
+              priv->plug_window,
+              False, NoEventMask,
+              (XEvent*) &xconfigure);
+  meta_x11_error_trap_pop (priv->x11_display);
+}
+
+static void
+na_xembed_synchronize_size (NaXembed *xembed)
+{
+  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);
+  Display *xdisplay = meta_x11_display_get_xdisplay (priv->x11_display);
+  int x, y, width, height;
+
+  x = priv->root_x;
+  y = priv->root_y;
+  width = priv->request_width;
+  height = priv->request_height;
+
+  XMoveResizeWindow (xdisplay,
+                     priv->socket_window,
+                     x, y,
+                     width, height);
+
+  if (priv->plug_window)
+    {
+      meta_x11_error_trap_push (priv->x11_display);
+
+      if (width != priv->current_width ||
+          height != priv->current_height)
+        {
+          XMoveResizeWindow (xdisplay,
+                             priv->plug_window,
+                             0, 0,
+                             width, height);
+          if (priv->resize_count)
+            priv->resize_count--;
+
+          priv->current_width = width;
+          priv->current_height = height;
+        }
+
+      if (priv->need_map)
+        {
+          XMapWindow (xdisplay, priv->plug_window);
+          priv->need_map = FALSE;
+        }
+
+      while (priv->resize_count)
+        {
+          na_xembed_send_configure_event (xembed);
+          priv->resize_count--;
+        }
+
+      meta_x11_error_trap_pop (priv->x11_display);
+    }
+}
+
+static gboolean
+synchronize_size_cb (gpointer user_data)
+{
+  NaXembed *xembed = user_data;
+  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);
+
+  na_xembed_synchronize_size (xembed);
+  priv->resize_id = 0;
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+na_xembed_resize (NaXembed *xembed)
+{
+  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);
+  XSizeHints hints;
+  long supplied;
+
+  g_clear_handle_id (&priv->resize_id, g_source_remove);
+
+  meta_x11_error_trap_push (priv->x11_display);
+
+  priv->request_width = 1;
+  priv->request_height = 1;
+
+  if (XGetWMNormalHints (meta_x11_display_get_xdisplay (priv->x11_display),
+                         priv->plug_window,
+                         &hints, &supplied))
+    {
+      if (hints.flags & PMinSize)
+        {
+          priv->request_width = MAX (hints.min_width, 1);
+          priv->request_height = MAX (hints.min_height, 1);
+        }
+      else if (hints.flags & PBaseSize)
+        {
+          priv->request_width = MAX (hints.base_width, 1);
+          priv->request_height = MAX (hints.base_height, 1);
+        }
+    }
+
+  priv->have_size = TRUE;
+  meta_x11_error_trap_pop (priv->x11_display);
+
+  priv->resize_id = g_idle_add (synchronize_size_cb, xembed);
+}
+
+static gboolean
+na_xembed_get_info (NaXembed      *xembed,
+                    Window         xwindow,
+                    unsigned long *version,
+                    unsigned long *flags)
+{
+  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);
+  MetaX11Display *x11_display = priv->x11_display;
+  Display *xdisplay = meta_x11_display_get_xdisplay (x11_display);
+  Atom type;
+  int format;
+  unsigned long nitems, bytes_after;
+  unsigned char *data;
+  unsigned long *data_long;
+  int status;
+
+  meta_x11_error_trap_push (x11_display);
+  status = XGetWindowProperty (xdisplay,
+                               xwindow,
+                               priv->atom__XEMBED_INFO,
+                               0, 2, False,
+                               priv->atom__XEMBED_INFO,
+                               &type, &format,
+                               &nitems, &bytes_after, &data);
+  meta_x11_error_trap_pop (x11_display);
+
+  if (status != Success)
+    return FALSE;
+  if (type == None)
+    return FALSE;
+
+  if (type != priv->atom__XEMBED_INFO)
+    {
+      g_warning ("_XEMBED_INFO property has wrong type");
+      XFree (data);
+      return FALSE;
+    }
+
+  if (nitems < 2)
+    {
+      g_warning ("_XEMBED_INFO too short");
+      XFree (data);
+      return FALSE;
+    }
+
+  data_long = (unsigned long *)data;
+  if (version)
+    *version = data_long[0];
+  if (flags)
+    *flags = data_long[1] & XEMBED_MAPPED;
+
+  XFree (data);
+  return TRUE;
+}
+
+static void
+na_xembed_add_window (NaXembed  *xembed,
+                      Window     xid,
+                      gboolean   need_reparent)
+{
+  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);
+  Display *xdisplay = meta_x11_display_get_xdisplay (priv->x11_display);
+  XVisualInfo template = { 0, };
+  int n_xvisuals;
+  unsigned long version;
+  unsigned long flags;
+
+  priv->plug_window = xid;
+
+  meta_x11_error_trap_push (priv->x11_display);
+
+  XSelectInput (xdisplay,
+                priv->plug_window,
+                StructureNotifyMask | PropertyChangeMask);
+
+  if (meta_x11_error_trap_pop_with_return (priv->x11_display))
+    {
+      priv->plug_window = None;
+      return;
+    }
+
+  /* OK, we now will reliably get destroy notification on socket->plug_window */
+
+  meta_x11_error_trap_push (priv->x11_display);
+
+  if (need_reparent)
+    {
+      XSetWindowAttributes socket_attrs;
+      XWindowAttributes plug_attrs;
+      int result;
+
+      result = XGetWindowAttributes (xdisplay, priv->plug_window, &plug_attrs);
+      if (result == 0)
+        {
+          meta_x11_error_trap_pop (priv->x11_display);
+          priv->plug_window = None;
+          return;
+        }
+
+      template.visualid = plug_attrs.visual->visualid;
+      priv->xvisual_info =
+        XGetVisualInfo (meta_x11_display_get_xdisplay (priv->x11_display),
+                        VisualIDMask,
+                        &template,
+                        &n_xvisuals);
+
+      if (!priv->xvisual_info)
+        {
+          meta_x11_error_trap_pop (priv->x11_display);
+          priv->plug_window = None;
+          return;
+        }
+
+      priv->has_alpha = (priv->xvisual_info->depth >
+                         __builtin_popcount (priv->xvisual_info->red_mask |
+                                             priv->xvisual_info->green_mask |
+                                             priv->xvisual_info->blue_mask));
+
+      socket_attrs.override_redirect = True;
+
+      priv->socket_window =
+        XCreateWindow (xdisplay,
+                       meta_x11_display_get_xroot (priv->x11_display),
+                       -100, -100, 1, 1, 0,
+                       priv->xvisual_info->depth,
+                       InputOutput,
+                       plug_attrs.visual,
+                       CWOverrideRedirect,
+                       &socket_attrs);
+
+      XUnmapWindow (xdisplay, priv->plug_window); /* Shouldn't actually be necessary for XEMBED, but just in case */
+      XReparentWindow (xdisplay,
+                       priv->plug_window,
+                       priv->socket_window,
+                       0, 0);
+    }
+
+  priv->have_size = FALSE;
+
+  priv->xembed_version = -1;
+  if (na_xembed_get_info (xembed, priv->plug_window, &version, &flags))
+    {
+      priv->xembed_version = MIN (XEMBED_PROTOCOL_VERSION, version);
+      priv->is_mapped = (flags & XEMBED_MAPPED) != 0;
+    }
+  else
+    {
+      /* FIXME, we should probably actually check the state before we started */
+      priv->is_mapped = TRUE;
+    }
+
+  priv->need_map = priv->is_mapped;
+
+  meta_x11_error_trap_pop (priv->x11_display);
+
+  meta_x11_error_trap_push (priv->x11_display);
+  XFixesChangeSaveSet (xdisplay, priv->plug_window,
+                       SetModeInsert, SaveSetRoot, SaveSetUnmap);
+  meta_x11_error_trap_pop (priv->x11_display);
+
+  xembed_send_message (xembed,
+                      priv->plug_window,
+                      XEMBED_EMBEDDED_NOTIFY, 0,
+                      priv->socket_window,
+                      priv->xembed_version);
+
+  na_xembed_resize (xembed);
+
+  g_signal_emit (xembed, signals[PLUG_ADDED], 0);
+
+  XMapWindow (xdisplay, priv->socket_window);
+}
+
+static void
+na_xembed_handle_map_request (NaXembed *xembed)
+{
+  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);
+
+  if (!priv->is_mapped)
+    {
+      priv->is_mapped = TRUE;
+      priv->need_map = TRUE;
+      na_xembed_resize (xembed);
+    }
+}
+
+static void
+na_xembed_handle_unmap_notify (NaXembed *xembed)
+{
+  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);
+
+  if (priv->is_mapped)
+    {
+      priv->is_mapped = FALSE;
+      na_xembed_resize (xembed);
+    }
+}
+
+static void
+xembed_filter_func (MetaX11Display *x11_display,
+                    XEvent         *xevent,
+                    gpointer        user_data)
+{
+  NaXembed *xembed = user_data;
+  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);
+  Display *xdisplay = meta_x11_display_get_xdisplay (priv->x11_display);
+
+  if (priv->socket_window == None)
+    return;
+  if (xevent->xany.window != priv->socket_window &&
+      xevent->xany.window != priv->plug_window)
+    return;
+
+  switch (xevent->type)
+    {
+    case ClientMessage:
+      /* We choose to ignore all _XEMBED ClientEvent messages
+       * addressed to the socket.
+       */
+      break;
+    case CreateNotify:
+      {
+        XCreateWindowEvent *xcwe = &xevent->xcreatewindow;
+
+        if (!priv->plug_window)
+          na_xembed_add_window (xembed, xcwe->window, FALSE);
+
+        break;
+      }
+    case ConfigureRequest:
+      {
+        XConfigureRequestEvent *xcre = &xevent->xconfigurerequest;
+
+        if (!priv->plug_window)
+          na_xembed_add_window (xembed, xcre->window, FALSE);
+
+        if (priv->plug_window)
+          {
+            if (xcre->value_mask & (CWWidth | CWHeight))
+              {
+                priv->resize_count++;
+                na_xembed_resize (xembed);
+              }
+            else if (xcre->value_mask & (CWX | CWY))
+              {
+                na_xembed_send_configure_event (xembed);
+              }
+
+            /* Ignore stacking requests. */
+          }
+        break;
+      }
+    case DestroyNotify:
+      {
+        XDestroyWindowEvent *xdwe = &xevent->xdestroywindow;
+
+        /* Note that we get destroy notifies both from SubstructureNotify on
+         * our window and StructureNotify on socket->plug_window
+         */
+        if (priv->plug_window && (xdwe->window == priv->plug_window))
+          {
+            g_object_ref (xembed);
+            g_signal_emit (xembed, signals[PLUG_REMOVED], 0);
+            na_xembed_end_embedding (xembed);
+            g_object_unref (xembed);
+          }
+        break;
+      }
+    case MapRequest:
+      if (!priv->plug_window)
+        na_xembed_add_window (xembed, xevent->xmaprequest.window, FALSE);
+
+      if (priv->plug_window == xevent->xmaprequest.window)
+        na_xembed_handle_map_request (xembed);
+      break;
+    case PropertyNotify:
+      if (priv->plug_window &&
+          xevent->xproperty.window == priv->plug_window)
+        {
+          if (xevent->xproperty.atom == priv->atom_WM_NORMAL_HINTS)
+            {
+              priv->have_size = FALSE;
+              na_xembed_resize (xembed);
+            }
+          else if (xevent->xproperty.atom == priv->atom__XEMBED_INFO)
+            {
+              unsigned long flags;
+
+              if (na_xembed_get_info (xembed, priv->plug_window, NULL, &flags))
+                {
+                  gboolean was_mapped = priv->is_mapped;
+                  gboolean is_mapped = (flags & XEMBED_MAPPED) != 0;
+
+                  if (was_mapped != is_mapped)
+                    {
+                      if (is_mapped)
+                        {
+                          na_xembed_handle_map_request (xembed);
+                        }
+                      else
+                        {
+                          meta_x11_error_trap_push (priv->x11_display);
+                          XMapWindow (xdisplay, priv->plug_window);
+                          meta_x11_error_trap_pop (priv->x11_display);
+
+                          na_xembed_handle_unmap_notify (xembed);
+                        }
+                    }
+                }
+            }
+        }
+      break;
+    case ReparentNotify:
+      {
+        XReparentEvent *xre = &xevent->xreparent;
+
+        if (priv->plug_window == None &&
+            xre->parent == priv->socket_window)
+          {
+            na_xembed_add_window (xembed, xre->window, FALSE);
+          }
+        else if (priv->plug_window != None &&
+                 xre->window == priv->plug_window &&
+                 xre->parent != priv->socket_window)
+          {
+            g_object_ref (xembed);
+            g_signal_emit (xembed, signals[PLUG_REMOVED], 0);
+            na_xembed_end_embedding (xembed);
+            g_object_unref (xembed);
+          }
+
+        break;
+      }
+    case UnmapNotify:
+      if (priv->plug_window != None &&
+          xevent->xunmap.window == priv->plug_window)
+        na_xembed_handle_unmap_notify (xembed);
+      break;
+    }
+}
+
+static void
+na_xembed_set_property (GObject      *object,
+                        guint         prop_id,
+                        const GValue *value,
+                        GParamSpec   *pspec)
+{
+  NaXembed *xembed = NA_XEMBED (object);
+  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);
+
+  switch (prop_id)
+    {
+    case PROP_X11_DISPLAY:
+      priv->x11_display = g_value_get_object (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+na_xembed_get_property (GObject    *object,
+                        guint       prop_id,
+                        GValue     *value,
+                        GParamSpec *pspec)
+{
+  NaXembed *xembed = NA_XEMBED (object);
+  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);
+
+  switch (prop_id)
+    {
+    case PROP_X11_DISPLAY:
+      g_value_set_object (value, priv->x11_display);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+na_xembed_finalize (GObject *object)
+{
+  NaXembed *xembed = NA_XEMBED (object);
+  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);
+
+  g_clear_pointer (&priv->xvisual_info, XFree);
+
+  if (priv->x11_display && priv->event_func_id)
+    meta_x11_display_remove_event_func (priv->x11_display, priv->event_func_id);
+
+  if (priv->plug_window)
+    na_xembed_end_embedding (xembed);
+
+  G_OBJECT_CLASS (na_xembed_parent_class)->finalize (object);
+}
+
+static void
+na_xembed_constructed (GObject *object)
+{
+  NaXembed *xembed = NA_XEMBED (object);
+  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);
+  Display *xdisplay;
+
+  G_OBJECT_CLASS (na_xembed_parent_class)->constructed (object);
+
+  xdisplay = meta_x11_display_get_xdisplay (priv->x11_display);
+
+  priv->event_func_id =
+    meta_x11_display_add_event_func (priv->x11_display,
+                                     xembed_filter_func,
+                                     object,
+                                     NULL);
+
+  priv->atom__XEMBED = XInternAtom (xdisplay, "_XEMBED", False);
+  priv->atom__XEMBED_INFO = XInternAtom (xdisplay, "_XEMBED_INFO", False);
+  priv->atom_WM_NORMAL_HINTS = XInternAtom (xdisplay, "WM_NORMAL_HINTS", False);
+}
+
+static void
+na_xembed_class_init (NaXembedClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->set_property = na_xembed_set_property;
+  object_class->get_property = na_xembed_get_property;
+  object_class->finalize = na_xembed_finalize;
+  object_class->constructed = na_xembed_constructed;
+
+  signals[PLUG_ADDED] =
+    g_signal_new ("plug-added",
+                  G_OBJECT_CLASS_TYPE (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (NaXembedClass, plug_added),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 0);
+
+  signals[PLUG_REMOVED] =
+    g_signal_new ("plug-removed",
+                  G_OBJECT_CLASS_TYPE (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (NaXembedClass, plug_removed),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 0);
+
+  props[PROP_X11_DISPLAY] =
+    g_param_spec_object ("x11-display",
+                         "x11-display",
+                         "x11-display",
+                         META_TYPE_X11_DISPLAY,
+                         G_PARAM_READWRITE |
+                         G_PARAM_CONSTRUCT_ONLY);
+
+  g_object_class_install_properties (object_class, N_PROPS, props);
+}
+
+static void
+na_xembed_init (NaXembed *xembed)
+{
+}
+
+void
+na_xembed_add_id (NaXembed *xembed,
+                  Window    window)
+{
+  na_xembed_add_window (xembed, window, TRUE);
+}
+
+Window
+na_xembed_get_plug_window (NaXembed *xembed)
+{
+  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);
+
+  return priv->plug_window;
+}
+
+Window
+na_xembed_get_socket_window (NaXembed *xembed)
+{
+  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);
+
+  return priv->socket_window;
+}
+
+void
+na_xembed_set_root_position (NaXembed *xembed,
+                             int       x,
+                             int       y)
+{
+  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);
+
+  if (priv->root_x == x &&
+      priv->root_y == y)
+    return;
+
+  priv->root_x = x;
+  priv->root_y = y;
+
+  if (priv->resize_id == 0)
+    priv->resize_id = g_idle_add (synchronize_size_cb, xembed);
+}
+
+MetaX11Display *
+na_xembed_get_x11_display (NaXembed *xembed)
+{
+  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);
+
+  return priv->x11_display;
+}
+
+void
+na_xembed_get_size (NaXembed *xembed,
+                    int      *width,
+                    int      *height)
+{
+  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);
+
+  if (width)
+    *width = priv->request_width;
+  if (height)
+    *height = priv->request_height;
+}
+
+static void
+get_pixel_details (unsigned long  pixel_mask,
+                   int           *shift,
+                   int           *precision)
+{
+  unsigned long m = 0;
+  int s = 0;
+  int p = 0;
+
+  if (pixel_mask != 0)
+    {
+      m = pixel_mask;
+      while (!(m & 0x1))
+        {
+          s++;
+          m >>= 1;
+        }
+
+      while (m & 0x1)
+        {
+          p++;
+          m >>= 1;
+        }
+    }
+
+  if (shift)
+    *shift = s;
+  if (precision)
+    *precision = p;
+}
+
+void
+na_xembed_set_background_color (NaXembed           *xembed,
+                                const ClutterColor *color)
+{
+  NaXembedPrivate *priv = na_xembed_get_instance_private (xembed);
+  XVisualInfo *xvisual_info;
+  Display *xdisplay;
+  unsigned long pixel;
+
+  if (!priv->socket_window)
+    return;
+  if (!priv->xvisual_info)
+    return;
+
+  xvisual_info = priv->xvisual_info;
+
+  if (priv->has_alpha)
+    {
+      pixel = 0;
+    }
+  else
+    {
+      double red, green, blue;
+      int red_shift, red_prec, green_shift, green_prec, blue_shift, blue_prec;
+      unsigned int padding;
+
+      /* Shifting by >= width-of-type isn't defined in C */
+      if (xvisual_info->depth >= 32)
+        padding = 0;
+      else
+        padding = ((~(uint32_t)0)) << xvisual_info->depth;
+
+      red = (double) color->red / 255.0;
+      green = (double) color->green / 255.0;
+      blue = (double) color->blue / 255.0;
+
+      get_pixel_details (xvisual_info->red_mask, &red_shift, &red_prec);
+      get_pixel_details (xvisual_info->green_mask, &green_shift, &green_prec);
+      get_pixel_details (xvisual_info->blue_mask, &blue_shift, &blue_prec);
+
+      pixel = ~(xvisual_info->red_mask |
+                xvisual_info->green_mask |
+                xvisual_info->blue_mask |
+                padding);
+
+      pixel += (((int) (red * ((1 << red_prec) - 1))) << red_shift) +
+        (((int) (green * ((1 << green_prec) - 1))) << green_shift) +
+        (((int) (blue * ((1 << blue_prec) - 1))) << blue_shift);
+    }
+
+  xdisplay = meta_x11_display_get_xdisplay (priv->x11_display);
+
+  XSetWindowBackground (xdisplay, priv->socket_window, pixel);
+  XClearWindow (xdisplay, priv->socket_window);
+}
diff --git a/src/tray/na-xembed.h b/src/tray/na-xembed.h
new file mode 100644 (file)
index 0000000..ec46119
--- /dev/null
@@ -0,0 +1,63 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* na-xembed.h
+ * Copyright (C) 2022 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Carlos Garnacho <carlosg@gnome.org>
+ */
+
+#ifndef NA_XEMBED_H
+#define NA_XEMBED_H
+
+#include <meta/meta-x11-display.h>
+#include <glib-object.h>
+#include <X11/Xlib.h>
+
+G_BEGIN_DECLS
+
+#define NA_TYPE_XEMBED (na_xembed_get_type ())
+G_DECLARE_DERIVABLE_TYPE (NaXembed, na_xembed, NA, XEMBED, GObject)
+
+struct _NaXembedClass
+{
+  GObjectClass parent_class;
+
+  void (* plug_added) (NaXembed *xembed);
+  void (* plug_removed) (NaXembed *xembed);
+};
+
+MetaX11Display * na_xembed_get_x11_display (NaXembed *xembed);
+
+void na_xembed_add_id (NaXembed *xembed,
+                      Window    window);
+
+Window na_xembed_get_plug_window (NaXembed *xembed);
+
+Window na_xembed_get_socket_window (NaXembed *xembed);
+
+void na_xembed_set_root_position (NaXembed *xembed,
+                                 int       x,
+                                 int       y);
+
+void na_xembed_get_size (NaXembed *xembed,
+                        int      *width,
+                        int      *height);
+
+void na_xembed_set_background_color (NaXembed           *xembed,
+                                    const ClutterColor *color);
+
+G_END_DECLS
+
+#endif /* NA_XEMBED_H */